package com.sxf.biz.operator.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sxf.auth.enums.CommonStatusEnum;
import com.sxf.auth.enums.SettleStatusEnum;
import com.sxf.auth.enums.UserTypeEnum;
import com.sxf.auth.login.dao.AuthUserMapper;
import com.sxf.auth.login.model.AuthUser;
import com.sxf.auth.login.service.UserService;
import com.sxf.auth.login.vo.*;
import com.sxf.biz.customer.dao.CenterMapper;
import com.sxf.biz.customer.dao.CustomerMapper;
import com.sxf.biz.customer.dao.ProjectMapper;
import com.sxf.biz.customer.dao.SettleMapper;
import com.sxf.biz.customer.service.CustomerService;
import com.sxf.biz.customer.vo.*;
import com.sxf.biz.model.Customer;
import com.sxf.biz.model.Project;
import com.sxf.biz.model.Settle;
import com.sxf.biz.operator.vo.*;
import com.sxf.cache.CacheUtil;
import com.sxf.cache.EhcacheElementType;
import com.sxf.common.enums.ErrorCodeEnum;
import com.sxf.common.exceptions.BusinessException;
import com.sxf.common.param.PageBaseVO;
import com.sxf.consts.YtConsts;
import com.sxf.core.aspect.UserThreadLocal;
import com.sxf.core.db.page.Pagination;
import com.sxf.utils.YtUtil;
import jodd.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: TODO
 * @Author Leo
 * @date 2024/3/2316:26
 */
@Service
public class OpService {
    private  static final Logger logger = LoggerFactory.getLogger(OpService.class);

    private static final String ACCOUT_PATTERN = "^\\w+[\\w-\\.]*@\\w+[\\w\\.]*$";
//    private static final String ACCOUT_PATTERN = "^([a-zA-Z0-9]+)@([a-zA-Z0-9.]+)$";
    private static final String ALIAS_PATTERN = "^\\w+[\\w\\.]*$";
//    private static final String ALIAS_PATTERN = "^[a-zA-Z0-9.]+$";

    private static final  Lock settleLock = new ReentrantLock();

    @Autowired
    UserService userService;
    @Autowired
    CustomerService customerService;

    @Autowired
    ProjectMapper projectMapper;
    @Autowired
    CustomerMapper customerMapper;
    @Autowired
    SettleMapper settleMapper;
    @Autowired
    AuthUserMapper authUserMapper;
    @Autowired
    CenterMapper centerMapper;

    @Autowired
    CacheUtil cacheUtil;

    public DashBoardRes getHomeDashBoard(){
        String userId = UserThreadLocal.getUserId();
        DashBoardRes res = new DashBoardRes();
        Integer prjCount = projectMapper.selectCount(new QueryWrapper<Project>());
        res.setProjectsCount(prjCount);
        Integer cusCount = customerMapper.selectCount(new QueryWrapper<Customer>().lambda().eq(Customer::getStatus, CommonStatusEnum.VALID.getCode()));
        res.setCustomerCount(cusCount);
        QueryWrapper<Settle> settleQueryWrapper = new QueryWrapper<>();
        settleQueryWrapper.select("IFNULL(sum(settle_mny),0) as settle_mny").eq("status",SettleStatusEnum.PAYED.getCode());
        Settle settle = settleMapper.selectOne(settleQueryWrapper);
        res.setTotalSettledMny(settle.getSettleMny());
        int subjectsCount = userService.count(new QueryWrapper<AuthUser>().lambda().eq(AuthUser::getUserType, UserTypeEnum.SUBJECTS.getCode()));
        res.setSubjectsCount(subjectsCount);
        return res;
    }

    public Pagination<CustomerListRes> getCustomerByPage(CustomerListReq req){
        return customerMapper.getCustomerListByPage(new Pagination<>(req),req);
    }

    public CustomerDetailRes getCustomerDetail(CustomerDetailReq req){
        return customerMapper.getCustomerDetail(req);
    }
    @Transactional(rollbackFor = Exception.class)
    public Boolean addCustomer(AddCustomerReq req){
        QueryWrapper<Customer> customerQueryWrapper = new QueryWrapper<>();
        customerQueryWrapper.lambda().eq(Customer::getCustomerName,req.getCustomerName())
                .or()
                .eq(Customer::getCustomerAlias,req.getCustomerAlias());

        Integer count = customerMapper.selectCount(customerQueryWrapper);
        if(count > 0){
            throw new BusinessException(ErrorCodeEnum.CUSTOMER_EXIST_ERROR);
        }
        AuthUser user = userService.getOne(new QueryWrapper<AuthUser>().lambda().eq(AuthUser::getAccount, req.getAccount()));
        if(user != null){
            throw new BusinessException(ErrorCodeEnum.USER_EXIST_ERROR);
        }
        //账号格式校验
        if(!YtUtil.validStrPattern(ALIAS_PATTERN,req.getCustomerAlias())){
            throw new BusinessException("客户账号后缀只能包含数字、字母");
        }
        if(!YtUtil.validStrPattern(ACCOUT_PATTERN,req.getAccount())){
            throw new BusinessException("管理员账号格式需要满足数字字母+@+客户账号后缀");
        }
        String[] accounts = req.getAccount().split("@");
        if(accounts.length != 2){
            throw new BusinessException("管理员账号格式需要满足数字字母+@+客户账号后缀");
        }

        if(!req.getCustomerAlias().equals(accounts[1])){
            throw new BusinessException("客户账号后缀和管理员账号后缀必须保持一致");
        }

        AddOpUserReq admin = new AddOpUserReq();
        admin.setAccount(req.getAccount());
        admin.setUserMobile(req.getUserMobile());
        admin.setUserName(req.getUserName());
        AddUserRes addUserRes = userService.addCutomerAdmin(admin);

        Customer customer = new Customer();
        customer.setCustomerName(req.getCustomerName());
        customer.setCustomerAlias(req.getCustomerAlias());
        customer.setAddress(req.getAddress());
        customer.setPkAdmin(addUserRes.getPkUser());
        customer.setCreator(req.getUserId());
        customer.setCreatorName(req.getCurName());
        customer.setCreatedTime(new Date());

        customerMapper.insert(customer);
        return true;
    }

    public Pagination<CenterListRes> getCenterByPage(OpCenterListReq req){
        Pagination<CenterListRes> centerListByPage4Op = centerMapper.getCenterListByPage4Op(new Pagination<>(req), req);
        return centerListByPage4Op;
    }


    public Boolean resetCustomerPwd(ResetPasswordReq req){
        return userService.resetPassword(req);
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean editCustomer(EditCustomerReq req){
        Customer customer = customerMapper.selectById(req.getPkCustomer());
        if(customer == null){
            throw new BusinessException(ErrorCodeEnum.CUSTOMER_NOT_EXIST_ERROR);
        }
        QueryWrapper<Customer> queryWrapper = new QueryWrapper<>();
        if(!StringUtil.isEmpty(req.getCustomerName())){
            queryWrapper.lambda().eq(Customer::getCustomerName,req.getCustomerName()).ne(Customer::getPkCustomer,req.getPkCustomer());
            Integer count = customerMapper.selectCount(queryWrapper);
            if(count > 0){
                throw new BusinessException(ErrorCodeEnum.CUSTOMER_EXIST_ERROR);
            }
        }
        if(!StringUtil.isEmpty(req.getAccount())){
            //账号格式是否正确
            if(!YtUtil.validStrPattern(ACCOUT_PATTERN,req.getAccount())){
                throw new BusinessException("管理员账号格式需要满足数字字母+@+客户账号后缀");
            }
            String[] accounts = req.getAccount().split("@");
            if(accounts.length != 2){
                throw new BusinessException("管理员账号格式需要满足数字字母+@+客户账号后缀");
            }

            if(!customer.getCustomerAlias().equals(accounts[1])){
                throw new BusinessException("管理员账号后缀必须和客户账号后缀保持一致");
            }
            AuthUser user = userService.getById(customer.getPkAdmin());
            if(!user.getAccount().equals(req.getAccount())){
                QueryWrapper<AuthUser> authUserQueryWrapper = new QueryWrapper<>();
                authUserQueryWrapper.lambda().eq(AuthUser::getAccount,req.getAccount());
                AuthUser existUser = userService.getOne(authUserQueryWrapper);
                if(existUser != null){
                    throw new BusinessException(ErrorCodeEnum.USER_EXIST_ERROR);
                }
                user.setAccount(req.getAccount());
            }
            if(!StringUtils.isEmpty(req.getUserName())){
                user.setUserName(req.getUserName());
            }
            if(!StringUtils.isEmpty(req.getUserMobile())){
                user.setUserMobile(req.getUserMobile());
            }
            userService.updateById(user);
        }
        BeanUtils.copyProperties(req,customer);
        customer.setModifier(req.getUserId());
        customer.setModifierName(req.getCurName());
        customer.setModifiedTime(new Date());
        customerMapper.updateById(customer);
        return true;
    }

    public Pagination<ProjectListRes> getProjectList(ProjectListReq req){
        return projectMapper.getProjectListByPage4Op(new Pagination<>(req),req);
    }

    public ProjectDetailRes getProjectDetail(ProjectDetailReq req){
        return customerService.getProjectDetail(req);
    }

    public Pagination<SubjectsUserInfoListRes> getSubjectList(SubjectsUserInfoListReq req){
        return userService.getSubjectsList(req);
    }

    public SubjectDetailRes getSubjectDetail(SubjectDetailReq req){
        return userService.getSubjectsUserInfo(req);
    }

    public Pagination<ProjectListRes> getProjectList4Subject(SubjectDetailReq req){
        return projectMapper.getProjectList4SubjectsByPage(new Pagination<>(req),req);
    }

    public SettlePageRes getSettleByPage(OpSettleListReq req){
        SettlePageRes res = new SettlePageRes();
        Pagination<SettleListRes> settleListByPage4Op = settleMapper.getSettleListByPage4Op(new Pagination<>(req), req);
        SettleStaticsResVo settleStatics4Op = settleMapper.getSettleStatics4Op(req);
        res.setSettleList(settleListByPage4Op);
        if(settleStatics4Op != null){
            if(settleStatics4Op.getUnsettleMny() == null){
                settleStatics4Op.setUnsettleMny(BigDecimal.ZERO);
            }
            if(settleStatics4Op.getSettledMny() == null){
                settleStatics4Op.setSettledMny(BigDecimal.ZERO);
            }
            res.setUnsettledTotalMny(settleStatics4Op.getUnsettleMny());
            res.setSettledTotalMny(settleStatics4Op.getSettledMny());
            res.setTotalMny(settleStatics4Op.getUnsettleMny().add(settleStatics4Op.getSettledMny()));
        }
        return res;
    }

    public SettleDetail4OpRes getSettleDetail(SettleDetailReq req){
        SettleDetail4OpRes settleDetail4Op = settleMapper.getSettleDetail4Op(req);
        if(settleDetail4Op != null){
            if(!StringUtils.isEmpty(settleDetail4Op.getImgs())){
                String[] imgList = settleDetail4Op.getImgs().split(";");
                settleDetail4Op.setImgList(Arrays.asList(imgList));
            }

            List<SettleStatusVo> settleStatus = customerService.getSettleStatus(req);
            settleDetail4Op.setSettleStatus(settleStatus);
        }
        return settleDetail4Op;
    }


    @Transactional(rollbackFor = Exception.class)
    public Boolean paySuccess(SettleDetailReq req){
        settleLock.lock();
        try{
            Settle settle = settleMapper.selectById(req.getPkSettle());
            if(settle == null){
                throw new BusinessException(ErrorCodeEnum.SETTLE_NOT_EXIST_ERROR);
            }
            if(!SettleStatusEnum.APPROVED.getCode().equals(settle.getStatus())){
                throw new BusinessException(ErrorCodeEnum.SETTLE_STATUS_NOT_APPROVED_ERROR);
            }
            settle.setStatus(SettleStatusEnum.PAYED.getCode());
            settle.setModifiedTime(new Date());
            settle.setModifier(req.getUserId());
            settle.setModifierName(req.getCurName());
            settleMapper.updateById(settle);
            customerService.addSettleStatus(settle,req,"付款成功");
        }finally {
            settleLock.unlock();
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public Boolean batchPaySuccess(SettleBatchPaySuccessReq req){
        settleLock.lock();
        try{
            for(Long pkSettle : req.getPkSettles()){
                Settle settle = settleMapper.selectById(pkSettle);
                if(settle == null){
                    logger.error("批量付款成功-计算单-{}不存在",pkSettle);
                   continue;
                }
                if(!SettleStatusEnum.APPROVED.getCode().equals(settle.getStatus())){
                    logger.error("批量付款成功-计算单-{}状态不是审批通过",pkSettle);
                    continue;
                }
                settle.setStatus(SettleStatusEnum.PAYED.getCode());
                settle.setModifiedTime(new Date());
                settle.setModifier(req.getUserId());
                settle.setModifierName(req.getCurName());
                settleMapper.updateById(settle);
                customerService.addSettleStatus(settle,req,"付款成功");
            }
        }finally {
            settleLock.unlock();
        }
        return true;
    }


    @Transactional(rollbackFor = Exception.class)
    public Boolean payFailed(SettleRejectReq req){
        settleLock.lock();
        try{
            Settle settle = settleMapper.selectById(req.getPkSettle());
            if(settle == null){
                throw new BusinessException(ErrorCodeEnum.SETTLE_NOT_EXIST_ERROR);
            }
            if(!SettleStatusEnum.APPROVED.getCode().equals(settle.getStatus())){
                throw new BusinessException(ErrorCodeEnum.SETTLE_STATUS_NOT_APPROVED_ERROR);
            }
            settle.setStatus(SettleStatusEnum.FAILED.getCode());
            settle.setRejectReason(req.getRejectReason());
            settle.setModifiedTime(new Date());
            settle.setModifier(req.getUserId());
            settle.setModifierName(req.getCurName());
            settleMapper.updateById(settle);
            customerService.addSettleStatus(settle,req,req.getRejectReason());
        } finally {
            settleLock.unlock();
        }
        return true;
    }

    public Pagination<OpUserListRes> getOpUserList(PageBaseVO req){
        SubjectsUserInfoListReq subjectsUserInfoListReq = new SubjectsUserInfoListReq();
        BeanUtils.copyProperties(req,subjectsUserInfoListReq);
        return userService.getOpUserList(subjectsUserInfoListReq);
    }

    public Boolean resetOpUserPwd(ResetPasswordReq req){
        return userService.resetPassword(req);
    }

    public Boolean enableOrDisable(ToggleOpUserReq req){
        if(req.getPkUser().equals(req.getUserId())){
            throw new BusinessException("无权操作自己账号");
        }
        AuthUser updateUser = userService.getById(req.getPkUser());
        if(updateUser == null){
            throw new BusinessException(ErrorCodeEnum.USER_NOT_EXIST_ERROR);
        }
        updateUser.setStatus(req.getStatus());
        updateUser.setModifier(req.getUserId());
        updateUser.setModifierName(req.getCurName());
        updateUser.setModifiedTime(new Date());
        return userService.updateById(updateUser);
    }

    public AddUserRes addOpUserPwd(AddOpUserReq req){
        if(!YtUtil.validStrPattern(ACCOUT_PATTERN,req.getAccount())){
            throw new BusinessException("用户账号格式需要满足数字字母+@+客户账号后缀");
        }
        return userService.addOpUser(req);
    }

    public void exportSettle(OpSettleListReq req, HttpServletResponse response){

        LoginRes loginRes = (LoginRes) cacheUtil.get(EhcacheElementType.TOKEN, YtConsts.USER_TOKEN_KEY + req.getAuthorization());
        if(loginRes == null){
            throw new BusinessException(ErrorCodeEnum.TOKEN_CHECK_ERROR);
        }

        if (!UserTypeEnum.PLATFORM.getCode().equals(loginRes.getUserType())) {
            throw new BusinessException(ErrorCodeEnum.UNAUTHORIZED_ERROR);
        }

        //单次导出条数不大于500
        req.setPageSize(500);
        req.setPage(1);
        List<SettleListRes> dataList = settleMapper.getSettleListForExport( req);

        ExcelWriter writer = null;
        String excelName = "结算单导出列表";
        try{
            writer = new ExcelWriter(true, "结算单列表");
            if(CommonStatusEnum.VALID.getCode().equals(req.getFullData())){
                writerFullDataExcel(dataList, writer);
            }else{
                writerSimpleDataExcel(dataList,writer);
            }
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            response.setHeader("content-disposition", "inline;filename=" + new String(excelName.getBytes(), "ISO8859-1") + ".xlsx");
            for (int i = 0; i < 14; i++) {
                writer.setColumnWidth(i, 18);
            }
            writer.flush(response.getOutputStream(), true);
        }catch (Exception e){
            logger.error("excel 文件导出失败",e);
        }finally {
            writer.close();
        }
    }

    private void writerFullDataExcel(List<SettleListRes> dataList, ExcelWriter writer) {
        List<String> title = CollUtil.newArrayList("金额", "付款类型", "所属项目", "所属中心", "提交时间", "筛选号", "随机号", "受试者姓名","受试者姓名缩写", "手机号","银行卡号","开户行信息","状态","描述");
        writer.writeHeadRow(title);
        //设置标题别名
        writer.setOnlyAlias(true);
        writer.addHeaderAlias("settleMny", "金额");
        writer.addHeaderAlias("payType", "付款类型");
        writer.addHeaderAlias("projectName", "所属项目");
        writer.addHeaderAlias("centerName", "所属中心");
        writer.addHeaderAlias("requestDate", "提交时间");
        writer.addHeaderAlias("subjectsSelectNo", "筛选号");
        writer.addHeaderAlias("subjectsRandomNo", "随机号");
        writer.addHeaderAlias("subjectsName", "受试者姓名");
        writer.addHeaderAlias("subjectsAlias", "受试者姓名缩写");
        writer.addHeaderAlias("userMobile", "手机号");
        writer.addHeaderAlias("accountNo", "银行卡号");
        writer.addHeaderAlias("accountBranch", "开户行信息");
        writer.addHeaderAlias("status", "状态");
        writer.addHeaderAlias("remark", "描述");
        writer.write(dataList);
    }

    private void writerSimpleDataExcel(List<SettleListRes> dataList, ExcelWriter writer) {
        List<String> title = CollUtil.newArrayList("金额", "付款类型", "所属项目", "所属中心", "提交时间", "筛选号", "随机号", "受试者姓名缩写", "状态","描述");
        writer.writeHeadRow(title);
        //设置标题别名
        writer.setOnlyAlias(true);
        writer.addHeaderAlias("settleMny", "金额");
        writer.addHeaderAlias("payType", "付款类型");
        writer.addHeaderAlias("projectName", "所属项目");
        writer.addHeaderAlias("centerName", "所属中心");
        writer.addHeaderAlias("requestDate", "提交时间");
        writer.addHeaderAlias("subjectsSelectNo", "筛选号");
        writer.addHeaderAlias("subjectsRandomNo", "随机号");
        writer.addHeaderAlias("subjectsAlias", "受试者姓名缩写");
        writer.addHeaderAlias("status", "状态");
        writer.addHeaderAlias("remark", "描述");
        writer.write(dataList);
    }
}
